/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Benjamin Muskalla -Bug 29633 * Helena Halperin - Bug 298747 * Andrey Loskutov <loskutov@gmx.de> - Bug 378485, 460555, 463262 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654 * Patrik Suzzi <psuzzi@gmail.com> - Bug 486859 *******************************************************************************/ package org.eclipse.ui.dialogs; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.util.Util; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.osgi.util.NLS; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.program.Program; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Tree; import org.eclipse.ui.IEditorDescriptor; import org.eclipse.ui.IEditorRegistry; import org.eclipse.ui.IFileEditorMapping; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.IWorkbenchHelpContextIds; import org.eclipse.ui.internal.WorkbenchMessages; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.registry.EditorDescriptor; import org.eclipse.ui.internal.registry.EditorRegistry; import org.eclipse.ui.internal.registry.FileEditorMapping; import org.eclipse.ui.progress.IProgressService; import org.eclipse.ui.statushandlers.StatusManager; /** * This class is used to allow the user to select a dialog from the set of * internal and external editors. * * @since 3.3 * @noextend This class is not intended to be subclassed by clients. */ public class EditorSelectionDialog extends Dialog { private static class TreeArrayContentProvider implements ITreeContentProvider { private static final Object[] EMPTY = new Object[0]; @Override public void dispose() { // } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // } @Override public Object[] getElements(Object inputElement) { if (inputElement == null || !inputElement.getClass().isArray()) { return EMPTY; } // see bug 9262 why we can't return the same array Object[] orig = (Object[]) inputElement; Object[] arr = new Object[orig.length]; System.arraycopy(orig, 0, arr, 0, arr.length); return arr; } @Override public Object[] getChildren(Object parentElement) { return EMPTY; } @Override public Object getParent(Object element) { return null; } @Override public boolean hasChildren(Object element) { return false; } } private IEditorDescriptor selectedEditor; private IEditorDescriptor hiddenSelectedEditor; private Button externalButton; private FilteredTree editorTable; private Button browseExternalEditorsButton; private Button internalButton; private Button okButton; /** * For internal use only. * * @noreference This field is not intended to be referenced by clients. * @since 3.7 */ protected static final String STORE_ID_INTERNAL_EXTERNAL = "EditorSelectionDialog.STORE_ID_INTERNAL_EXTERNAL";//$NON-NLS-1$ private static final String STORE_ID_DESCR = "EditorSelectionDialog.STORE_ID_DESCR";//$NON-NLS-1$ private static final String STORE_ID_FILE_EXTENSION = "EditorSelectionDialog.STORE_ID_FILE_EXTENSION";//$NON-NLS-1$ private String message = WorkbenchMessages.EditorSelection_chooseAnEditor; // collection of IEditorDescriptor private IEditorDescriptor[] externalEditors; private IEditorDescriptor[] internalEditors; private IEditorDescriptor[] editorsToFilter; private DialogListener listener = new DialogListener(); private ResourceManager resourceManager; private TreeViewer editorTableViewer; private String fileName; private Button rememberTypeButton; private Button rememberEditorButton; private static final String[] Executable_Filters; private static final int TABLE_WIDTH = 200; static { if (Util.isWindows()) { Executable_Filters = new String[] { "*.exe", "*.bat", "*.*" };//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } else if (Util.isMac()) { Executable_Filters = new String[] { "*.app", "*" }; //$NON-NLS-1$ //$NON-NLS-2$ } else { Executable_Filters = new String[] { "*" }; //$NON-NLS-1$ } } /** * Create an instance of this class. * * @param parentShell * the parent shell */ public EditorSelectionDialog(Shell parentShell) { super(parentShell); resourceManager = new LocalResourceManager(JFaceResources.getResources(parentShell .getDisplay())); } /** * This method is called if a button has been pressed. */ @Override protected void buttonPressed(int buttonId) { if (buttonId == IDialogConstants.OK_ID) { saveWidgetValues(); } super.buttonPressed(buttonId); } /** * Close the window. */ @Override public boolean close() { boolean result = super.close(); resourceManager.dispose(); resourceManager = null; return result; } @Override protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText(WorkbenchMessages.EditorSelection_title); PlatformUI.getWorkbench().getHelpSystem().setHelp(shell, IWorkbenchHelpContextIds.EDITOR_SELECTION_DIALOG); } /** * Creates and returns the contents of the upper part of the dialog (above * the button bar). * * Subclasses should overide. * * @param parent * the parent composite to contain the dialog area * @return the dialog area control */ @Override protected Control createDialogArea(Composite parent) { Font font = parent.getFont(); // create main group Composite contents = (Composite) super.createDialogArea(parent); ((GridLayout) contents.getLayout()).numColumns = 2; // begin the layout Label textLabel = new Label(contents, SWT.WRAP); textLabel.setText(message); GridData data = new GridData(); data.horizontalSpan = 2; data.horizontalAlignment = SWT.FILL; data.widthHint = TABLE_WIDTH; textLabel.setLayoutData(data); textLabel.setFont(font); Composite group = new Composite(contents, SWT.SHADOW_NONE); data = new GridData(); data.grabExcessHorizontalSpace = true; data.horizontalAlignment = SWT.FILL; data.horizontalSpan = 2; group.setLayout(new RowLayout(SWT.HORIZONTAL)); group.setLayoutData(data); internalButton = new Button(group, SWT.RADIO | SWT.LEFT); internalButton.setText(WorkbenchMessages.EditorSelection_internal); internalButton.addListener(SWT.Selection, listener); internalButton.setFont(font); externalButton = new Button(group, SWT.RADIO | SWT.LEFT); externalButton.setText(WorkbenchMessages.EditorSelection_external); externalButton.addListener(SWT.Selection, listener); externalButton.setFont(font); editorTable = new FilteredTree(contents, SWT.SINGLE | SWT.BORDER, new PatternFilter(), true); editorTableViewer = editorTable.getViewer(); Tree tree = editorTableViewer.getTree(); tree.addListener(SWT.Selection, listener); tree.addListener(SWT.DefaultSelection, listener); tree.addListener(SWT.MouseDoubleClick, listener); data = new GridData(); data.widthHint = convertHorizontalDLUsToPixels(TABLE_WIDTH); data.horizontalAlignment = GridData.FILL; data.grabExcessHorizontalSpace = true; data.verticalAlignment = GridData.FILL; data.grabExcessVerticalSpace = true; data.horizontalSpan = 2; editorTable.setLayoutData(data); editorTable.setFont(font); data.heightHint = tree.getItemHeight() * 12; editorTableViewer.setContentProvider(new TreeArrayContentProvider()); editorTableViewer.setLabelProvider(new LabelProvider() { @Override public String getText(Object element) { IEditorDescriptor d = (IEditorDescriptor) element; return TextProcessor.process(d.getLabel(), "."); //$NON-NLS-1$ } @Override public Image getImage(Object element) { IEditorDescriptor d = (IEditorDescriptor) element; return (Image) resourceManager.get(d.getImageDescriptor()); } }); browseExternalEditorsButton = new Button(contents, SWT.PUSH); browseExternalEditorsButton .setText(WorkbenchMessages.EditorSelection_browse); browseExternalEditorsButton.addListener(SWT.Selection, listener); data = new GridData(); int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); data.widthHint = Math.max(widthHint, browseExternalEditorsButton .computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x); data.horizontalSpan = 2; browseExternalEditorsButton.setLayoutData(data); browseExternalEditorsButton.setFont(font); if (fileName != null) { rememberEditorButton = new Button(contents, SWT.CHECK | SWT.LEFT); rememberEditorButton.setText(NLS.bind(WorkbenchMessages.EditorSelection_rememberEditor, fileName)); rememberEditorButton.addListener(SWT.Selection, listener); data = new GridData(); data.horizontalSpan = 2; rememberEditorButton.setLayoutData(data); rememberEditorButton.setFont(font); String fileType = getFileType(); if (!fileType.isEmpty()) { rememberTypeButton = new Button(contents, SWT.CHECK | SWT.LEFT); rememberTypeButton.setText(NLS.bind(WorkbenchMessages.EditorSelection_rememberType, fileType)); rememberTypeButton.addListener(SWT.Selection, listener); data = new GridData(); data.horizontalSpan = 2; rememberTypeButton.setLayoutData(data); rememberTypeButton.setFont(font); } } initializeSuggestion(); restoreWidgetValues(); // Place buttons to the appropriate state // Run async to restore selection on *visible* dialog - otherwise three won't scroll PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { if (editorTable.isDisposed()) { return; } fillEditorTable(); updateEnableState(); }); return contents; } private String getFileType() { if (fileName == null) { return ""; //$NON-NLS-1$ } int lastDot = fileName.lastIndexOf('.'); if (lastDot == -1 || lastDot >= fileName.length() - 1) { return ""; //$NON-NLS-1$ } return fileName.substring(lastDot + 1, fileName.length()); } protected void fillEditorTable() { IEditorDescriptor newSelection = selectedEditor; boolean showInternal = internalButton.getSelection(); Object[] input = (Object[]) editorTableViewer.getInput(); if (input != null) { // we are switching between external/internal editors boolean isShowingInternal = Arrays.equals(input, getInternalEditors()); if (showInternal != isShowingInternal) { newSelection = hiddenSelectedEditor; if (!editorTableViewer.getSelection().isEmpty()) { hiddenSelectedEditor = (EditorDescriptor) editorTableViewer.getStructuredSelection() .getFirstElement(); } } } editorTableViewer.setInput(showInternal ? getInternalEditors() : getExternalEditors()); if (fileName != null && newSelection == null) { if (!showInternal) { newSelection = findBestExternalEditor(); } else { newSelection = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(fileName); } } if (newSelection != null) { editorTableViewer.setSelection(new StructuredSelection(newSelection), true); } if (editorTableViewer.getSelection().isEmpty()) { // set focus to first element, but don't select it: Tree tree = editorTableViewer.getTree(); if (tree.getItemCount() > 0) { tree.showItem(tree.getItem(0)); } } editorTable.setFocus(); } private static String getFileExtension(String fileName) { if (fileName == null) { return null; } int index = fileName.lastIndexOf('.'); if (index != -1) { return fileName.substring(index); } return fileName; } /** * Return the dialog store to cache values into */ protected IDialogSettings getDialogSettings() { IDialogSettings workbenchSettings = WorkbenchPlugin.getDefault() .getDialogSettings(); IDialogSettings section = workbenchSettings .getSection("EditorSelectionDialog");//$NON-NLS-1$ if (section == null) { section = workbenchSettings.addNewSection("EditorSelectionDialog");//$NON-NLS-1$ } return section; } /** * Get a list of registered programs from the OS */ protected IEditorDescriptor[] getExternalEditors() { if (externalEditors == null) { IProgressService ps = PlatformUI.getWorkbench().getService(IProgressService.class); // Since this can take a while, show the busy cursor. IRunnableWithProgress runnable = monitor -> { // Get the external editors available EditorRegistry reg = (EditorRegistry) WorkbenchPlugin.getDefault().getEditorRegistry(); externalEditors = reg.getSortedEditorsFromOS(); externalEditors = filterEditors(externalEditors); }; try { // See bug 47556 - Program.getPrograms() requires a Display.getCurrent() != null ps.runInUI(PlatformUI.getWorkbench().getActiveWorkbenchWindow(), runnable, null); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); IStatus status; if (cause instanceof CoreException) { status = ((CoreException) cause).getStatus(); } else { status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, "Error while retrieving native editors", cause); //$NON-NLS-1$ } StatusManager.getManager().handle(status); } catch (InterruptedException e) { // Canceled by the user } } return externalEditors; } /** * Returns an array of editors which have been filtered according to the * array of editors in the editorsToFilter instance variable. * * @param editors * an array of editors to filter * @return a filtered array of editors */ protected IEditorDescriptor[] filterEditors(IEditorDescriptor[] editors) { if ((editors == null) || (editors.length < 1)) { return editors; } if ((editorsToFilter == null) || (editorsToFilter.length < 1)) { return editors; } List<IEditorDescriptor> filteredList = new ArrayList<>(); for (IEditorDescriptor editor : editors) { boolean add = true; for (IEditorDescriptor element : editorsToFilter) { if (editor.getId().equals(element.getId())) { add = false; } } if (add) { filteredList.add(editor); } } return filteredList.toArray(new IEditorDescriptor[filteredList.size()]); } /** * Returns the internal editors */ protected IEditorDescriptor[] getInternalEditors() { if (internalEditors == null) { EditorRegistry reg = (EditorRegistry) WorkbenchPlugin.getDefault() .getEditorRegistry(); internalEditors = reg.getSortedEditorsFromPlugins(); internalEditors = filterEditors(internalEditors); } return internalEditors; } /** * Return the editor the user selected * * @return the selected editor */ public IEditorDescriptor getSelectedEditor() { return selectedEditor; } protected void promptForExternalEditor() { FileDialog dialog = new FileDialog(getShell(), SWT.OPEN | SWT.PRIMARY_MODAL | SWT.SHEET); dialog.setFilterExtensions(Executable_Filters); String result = dialog.open(); if (result != null) { EditorDescriptor editor = EditorDescriptor.createForProgram(result); /* * add to our collection of cached external editors in case the user * flips back and forth between internal/external */ IEditorDescriptor[] newEditors = new IEditorDescriptor[externalEditors.length + 1]; System.arraycopy(externalEditors, 0, newEditors, 0, externalEditors.length); newEditors[newEditors.length - 1] = editor; externalEditors = newEditors; editorTableViewer.setInput(externalEditors); editorTableViewer.setSelection(new StructuredSelection(editor), true); editorTable.setFocus(); selectedEditor = editor; } } /** * Handle a double click event on the list */ protected void handleDoubleClickEvent() { buttonPressed(IDialogConstants.OK_ID); } private void initializeSuggestion() { if (fileName == null) { return; } IEditorRegistry editorRegistry = PlatformUI.getWorkbench().getEditorRegistry(); IEditorDescriptor suggestion = editorRegistry.getDefaultEditor(fileName); if (suggestion != null && suggestion.isInternal()) { selectedEditor = suggestion; } else { selectedEditor = findBestExternalEditor(); } boolean enableInternalList = selectedEditor == null || selectedEditor.isInternal(); internalButton.setSelection(enableInternalList); externalButton.setSelection(!enableInternalList); } private IEditorDescriptor findBestExternalEditor() { if (fileName == null) { return null; } String extension = getFileExtension(fileName); Program program = Program.findProgram(extension); if (program != null) { for (IEditorDescriptor descriptor : getExternalEditors()) { if (descriptor instanceof EditorDescriptor && program.equals(((EditorDescriptor) descriptor).getProgram())) { return descriptor; } } } return null; } /** * Use the dialog store to restore widget values to the values that they * held last time this wizard was used to completion, if the previous file * has same extension. */ protected void restoreWidgetValues() { IDialogSettings settings = getDialogSettings(); if (fileName == null || selectedEditor == null || getFileExtension(fileName).equals(settings.get(STORE_ID_FILE_EXTENSION))) { boolean wasExternal = settings.getBoolean(STORE_ID_INTERNAL_EXTERNAL); internalButton.setSelection(!wasExternal); externalButton.setSelection(wasExternal); String id = settings.get(STORE_ID_DESCR); if (id != null) { IEditorDescriptor[] editors; if (wasExternal) { editors = getExternalEditors(); } else { editors = getInternalEditors(); } for (IEditorDescriptor desc : editors) { if (id.equals(desc.getId())) { selectedEditor = desc; } } } } } /** * Since Finish was pressed, write widget values to the dialog store so that * they will persist into the next invocation of this wizard page */ protected void saveWidgetValues() { IDialogSettings settings = getDialogSettings(); // record whether use was viewing internal or external editors settings.put(STORE_ID_FILE_EXTENSION, getFileExtension(fileName)); settings.put(STORE_ID_INTERNAL_EXTERNAL, !internalButton.getSelection()); settings.put(STORE_ID_DESCR, selectedEditor.getId()); String editorId = selectedEditor.getId(); settings.put(STORE_ID_DESCR, editorId); boolean associateEditor = false; EditorRegistry reg = (EditorRegistry) WorkbenchPlugin.getDefault().getEditorRegistry(); // remember editor for specific file if (rememberEditorButton != null && rememberEditorButton.getSelection()) { updateFileMappings(reg, true); reg.setDefaultEditor(fileName, selectedEditor); associateEditor = true; } // remember editor for given extension type if (rememberTypeButton != null && rememberTypeButton.getSelection()) { updateFileMappings(reg, false); reg.setDefaultEditor("*." + getFileType(), selectedEditor); //$NON-NLS-1$ associateEditor = true; } if (associateEditor) { // bug 468906: always re-set editor mappings: this is needed to // rebuild internal editors map after setting the default editor List<IFileEditorMapping> newMappings = new ArrayList<>(); newMappings.addAll(Arrays.asList(reg.getFileEditorMappings())); reg.setFileEditorMappings(newMappings.toArray(new FileEditorMapping[newMappings.size()])); reg.saveAssociations(); } } /** * Make sure EditorRegistry has editor mapping for the file name/type */ private void updateFileMappings(EditorRegistry reg, boolean useFileName) { IFileEditorMapping[] mappings = reg.getFileEditorMappings(); boolean hasMapping = false; String fileType = getFileType(); for (IFileEditorMapping mapping : mappings) { if (useFileName) { if (fileName.equals(mapping.getLabel())) { hasMapping = true; break; } } else { if (fileType.equals(mapping.getExtension())) { hasMapping = true; break; } } } if (hasMapping) { return; } FileEditorMapping mapping; if (useFileName) { mapping = new FileEditorMapping(fileName, null); } else { mapping = new FileEditorMapping(null, fileType); } List<IFileEditorMapping> newMappings = new ArrayList<>(); newMappings.addAll(Arrays.asList(mappings)); newMappings.add(mapping); FileEditorMapping[] array = newMappings.toArray(new FileEditorMapping[newMappings.size()]); reg.setFileEditorMappings(array); } /** * Set the message displayed by this message dialog * * @param aMessage * the message */ public void setMessage(String aMessage) { message = aMessage; } /** * Set the file name which can be used to store the selected editor * preference * * @param fileName * the file name * @since 3.107 */ public void setFileName(String fileName) { this.fileName = fileName; } /** * Set the editors which will not appear in the dialog. * * @param editors * an array of editors */ public void setEditorsToFilter(IEditorDescriptor[] editors) { editorsToFilter = editors; } /** * Update enabled state. */ protected void updateEnableState() { boolean enableExternal = externalButton.getSelection(); browseExternalEditorsButton.setEnabled(enableExternal); updateOkButton(); } @Override protected void createButtonsForButtonBar(Composite parent) { okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); // initially there is no selection so OK button should not be enabled okButton.setEnabled(false); } /** * Update the button enablement state. */ protected void updateOkButton() { // Buttons are null during dialog creation if (okButton == null) { return; } // If there is no selection, do not enable OK button if (editorTableViewer.getSelection().isEmpty()) { okButton.setEnabled(false); return; } // At this point, there is a selection okButton.setEnabled(selectedEditor != null); } private class DialogListener implements Listener { @Override public void handleEvent(Event event) { if (event.type == SWT.MouseDoubleClick) { handleDoubleClickEvent(); return; } if (event.widget == externalButton) { fillEditorTable(); } else if (event.widget == browseExternalEditorsButton) { promptForExternalEditor(); } else if (event.widget == editorTableViewer.getTree()) { if (!editorTableViewer.getSelection().isEmpty()) { selectedEditor = (EditorDescriptor) editorTableViewer.getStructuredSelection().getFirstElement(); } else { selectedEditor = null; okButton.setEnabled(false); } } // 486859 both checked: checking one box unchecks the other if (rememberEditorButton != null && rememberTypeButton != null && rememberEditorButton.getSelection() && rememberTypeButton.getSelection()) { if (event.widget == rememberEditorButton) { rememberTypeButton.setSelection(false); } if (event.widget == rememberTypeButton) { rememberEditorButton.setSelection(false); } } updateEnableState(); } } @Override protected boolean isResizable() { return true; } }